Amazon GameLift In C# 09: より良いチャットサーバーを作ります(Part2)
概要
前回の記事はChatServer.cs
のソースコードを解説しました。今回はConnectedClient.cs
を中心に より良いチャットサーバーののPart2 の記事です。
マシン環境
CPU : Intel i7-6920HQ
GPU : AMD Radeon Pro 460
Memory : 16GB
System : Windows 10 (with .NET 5 installed)
IDE : Visual Studio 2019 Community
Editor : Visual Studio Code
Terminal : Windows Terminal (or Command Prompt)
ファイルの構造とクラスの運用
プロジェクトはGitHubにアップロードしています : AGLW-CSharp-BetterChatServerSample
ソースコードファイル :
- Program.cs (プログラムの入口)
- GameLiftServer.cs (Amazon GameLiftとの接続、そして
ChatServer
を管理するクラス) - ChatServer.cs (クライアント
ConnectedClient
との接続、通信管理クラス) - ConnectedClient.cs (クライアントの接続情報と
NetworkStream
での受送信管理クラス)
ChatServer
は接続している複数のConnectedClient
を管理しています。ChatServer
は全体クライアントとの通信を管理していますが、細かいメッセージ処理などはConnectedClient
でやっています(NetworkStream周り)。
ConnectedClient.cs ソースコード
ConnectedClient.cs
ソースコードを貼り付けます。
class ConnectedClient { public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8; public TcpClient TargetClient { get; private set; } = null; public NetworkStream TargetStream { get; private set; } = null; public ConcurrentQueue<byte[]> SendingQueue { get; private set; } = null; public ConcurrentQueue<byte[]> ReceivingQueue { get; private set; } = null; public Thread SenderThread { get; private set; } = null; public Thread ReceiverThread { get; private set; } = null; public ConnectedClient(TcpClient client) { TargetClient = client; TargetStream = client.GetStream(); SendingQueue = new ConcurrentQueue<byte[]>(); ReceivingQueue = new ConcurrentQueue<byte[]>(); } public void StartClient() { SenderThread = new Thread(() => Send()); SenderThread.Start(); ReceiverThread = new Thread(() => Receive()); ReceiverThread.Start(); } // Should be called by server, add a message to queue, will be sent to client later public void SendMessage(byte[] bytes) { SendingQueue.Enqueue(bytes); } // Should be called by server, retrieve message public bool RetrieveMessage(out byte[] bytes) { bool retrieved = ReceivingQueue.TryDequeue(out bytes); return retrieved; } // Looping in a sender thread private void Send() { byte[] bytes; while (TargetStream != null) { if (SendingQueue.TryDequeue(out bytes)) { TargetStream.Write(bytes); } } } // Looping in a receiver thread private void Receive() { byte[] bytes = new byte[ChatServer.MessageLength]; if (TargetStream != null) { try { while (TargetStream.Read(bytes) > 0) { Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}"); ReceivingQueue.Enqueue(bytes); bytes = new byte[ChatServer.MessageLength]; } } catch(SocketException e) { Console.WriteLine($"Excpetion catched : {e}"); } catch(IOException e) { Console.WriteLine($"Excpetion catched : {e}"); } } } }
ConnectedClient.cs メンバー変数の解説
public readonly System.Text.Encoding Encoder = System.Text.Encoding.UTF8; public TcpClient TargetClient { get; private set; } = null; public NetworkStream TargetStream { get; private set; } = null; public ConcurrentQueue<byte[]> SendingQueue { get; private set; } = null; public ConcurrentQueue<byte[]> ReceivingQueue { get; private set; } = null; public Thread SenderThread { get; private set; } = null; public Thread ReceiverThread { get; private set; } = null;
- TcpClient :
TargetClient
ChatServer
から渡してきたクライアント接続情報を持つTcpClient
です。 - NetworkStream :
TargetStream
TargetClient
のNetworkStream
です。メッセージ受送信を担当しています。 -
ConcurrentQueue<byte[]> :
SendingQueue
/ReceivingQueue
メッセージの受送信のは一瞬でやるではなく、これから送信するメッセージはSendingQueue
に保存して送信を待ち、受信したメッセージはReceivingQueue
に保存してChatServer
は後ほど集めます。 - Thread :
SenderThread
/ReceiverThread
送信することはSenderThread
で行い、受送することはReceiverThread
で行います。
ConnectedClient.cs 関数の解説
// Should be called by server, add a message to queue, will be sent to client later public void SendMessage(byte[] bytes) { SendingQueue.Enqueue(bytes); } // Should be called by server, retrieve message public bool RetrieveMessage(out byte[] bytes) { bool retrieved = ReceivingQueue.TryDequeue(out bytes); return retrieved; }
SendMessage()
とRetrieveMessage()
はChatServer
で実行される関数です。
SendMessage()
は最初からDelegate
に登録されます。一斉送信される際は、すぐ送信ではなく、SendMessage
でSendingQueue
にメッセージを保存し、順序的に送信することを待ちます。
RetrieveMessage()
は一旦受信してReceivingQueue
に保存されるメッセージを取り出す関数です。ChatServer
で呼ばれて、ReceivingQueue
のメッセージを順序の並びで送信隊列に保存します。
// Looping in a sender thread private void Send() { byte[] bytes; while (TargetStream != null) { if (SendingQueue.TryDequeue(out bytes)) { TargetStream.Write(bytes); } } }
Send()
関数はSenderThread
で実行され、SendingQueue
に残っているメッセージを常に検出し、送信します。
// Looping in a receiver thread private void Receive() { byte[] bytes = new byte[ChatServer.MessageLength]; if (TargetStream != null) { try { while (TargetStream.Read(bytes) > 0) { Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}"); ReceivingQueue.Enqueue(bytes); bytes = new byte[ChatServer.MessageLength]; } } catch(SocketException e) { Console.WriteLine($"Excpetion catched : {e}"); } catch(IOException e) { Console.WriteLine($"Excpetion catched : {e}"); } } }
Receive()
はReceiverThread
で実行されます。
while (TargetStream.Read(bytes) > 0) { Console.WriteLine($"Message Received: {Encoder.GetString(bytes)}"); ReceivingQueue.Enqueue(bytes); bytes = new byte[ChatServer.MessageLength]; }
TargetStream
(NetworkStream)に送信してきたメッセージを取り出して、ReceivingQueue
に保存します。
catch(SocketException e) { Console.WriteLine($"Excpetion catched : {e}"); } catch(IOException e) { Console.WriteLine($"Excpetion catched : {e}"); }
クライアントが接続した際、一瞬Exception
が出ます。catch
しないとプログラムが落ちるので、ここで予想したException
をcatch
して、落ちないようにします。
これでConnectedClients.cs
の説明が完了いたします。
最後
より良いチャットサーバーはその前簡単なチャットサーバー記事とGameLift接続部分が基本一緒ですが、それ以外の違う部分はこれで説明完了になります。
次回の記事はクライアントのソースコードを解説します。